#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <iostream>
#include <malloc.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "Buffer.h"

// Buffer manages writing and reading from HDD
// It buffers the input and output for speed reasons

#ifndef NOODIRECT
#define ODIRECT O_DIRECT
#else
#define ODIRECT 0
#endif

#define BUFFER_SIZE (100 * (size_t)getpagesize())
#define BUFFER_ALIGN (BUFFER_SIZE * 2)

Buffer::Buffer(): isInitialized(false) {
}

// opens files and prepares the write and read buffers (initialize memory)
bool Buffer::init(const char* input, const char* output) {
        inputfile = open(input, ODIRECT | O_RDONLY);
        if (inputfile == -1) {
                fputs("couldn't open input file\n", stderr);
                return false;
        }
        if (!initInput()) {
                return false;
        }

        outputfile = open(output, ODIRECT | O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
        if (outputfile == -1) {
                fputs("couldn't open output file\n", stderr);
                return false;
        }
        if (!initOutput()) {
                return false;
        }
        isInitialized = true;
        return true;
}

Buffer::~Buffer() {
	if(isInitialized) {
        // write everything in output buffer into file on HDD
        if(outputindex < (BUFFER_ALIGN - 1)){
                memset(&outputBuffer[outputindex], '\0', BUFFER_ALIGN - outputindex);
        }
        flush();
        close(inputfile);
        close(outputfile);
        free(inputBuffer);
        free(outputBuffer);
	}
}

bool Buffer::initInput() {
        isFinished = false;
        buffer1IsAlreadyChanged = false;
        buffer2IsAlreadyChanged = false;

        inputindex = 0;
        fileindex = 0;

        inputBuffer = (char*) memalign(BUFFER_SIZE, BUFFER_ALIGN);
        if (inputBuffer == NULL) {
                fputs("Input Buffer Memalign failed\n", stderr);
                return false;
        }

        // fill first half of buffer with content from file
        readfile = read(inputfile, &inputBuffer[inputindex], BUFFER_SIZE);
        if (readfile == -1) {
                fputs("couldn't read from file\n", stderr);
                return false;
        }
        return true;
}

bool Buffer::initOutput() {
        outputindex = 0;

        outputBuffer = (char*) memalign(BUFFER_ALIGN, BUFFER_ALIGN);
        if (outputBuffer == NULL) {
                fputs("Output Buffer Memalign failed\n", stderr);
                return false;
        }
        return true;
}

void Buffer::writeString(const char* printString) {
        // get length of string (string must be null terminated)
        size_t size = strlen(printString);
        for (size_t i = 0; i < size; ++i) {
                if (outputindex >= BUFFER_ALIGN) {
                        // if buffer is full -> write content to file
                        flush();
                        --i;
                        continue;
                }
                outputBuffer[outputindex++] = printString[i];
        }
}

void Buffer::flush() {
        writefile = write(outputfile, outputBuffer, BUFFER_ALIGN);
        outputindex = 0;
        if (writefile == -1) {
                fputs("couldn't write to file\n", stderr);
        }
}

bool Buffer::reloadBuffer(int position) {
        readfile = read(inputfile, &inputBuffer[position], BUFFER_SIZE);
        if (readfile == -1) {
                fputs("couldn't reload buffer\n", stderr);
                return false;
        }
        return true;
}

char Buffer::getChar() {
        inputindex = inputindex % BUFFER_ALIGN;
        // last sign -> end of file
        if (inputBuffer[inputindex] == '\0') {
                isFinished = true;
        }
        if (!isFinished) {
                if (inputindex == BUFFER_SIZE - 1) {
                        if (!buffer2IsAlreadyChanged) {
                                reloadBuffer(BUFFER_SIZE);
                                buffer2IsAlreadyChanged = true;
                                buffer1IsAlreadyChanged = false;
                        }
                } else if (inputindex == (BUFFER_SIZE * 2) - 1) {
                        if (!buffer1IsAlreadyChanged) {
                                reloadBuffer(0);
                                buffer1IsAlreadyChanged = true;
                                buffer2IsAlreadyChanged = false;
                        }
                }
                ++fileindex;
                return inputBuffer[inputindex++];
        } else {
                return '\0';
        }
}

// Go one step back in internal buffer
//return values:
//		 0 = success
//		-1 = string too long
//		-2 = Beginning of file reached
int Buffer::ungetChar(const size_t numberOfChars) {
	// if number of chars is longer than internal buffer -> doesn't work -> too long
	if (numberOfChars > BUFFER_SIZE) {
		return -1;
	}
	for (size_t i = 0; i < numberOfChars; i++) {
		if (fileindex <= 0) {
			return -2;
		}
		if (inputindex > 0) {
				--inputindex;
		} else if (inputindex == 0) {
				inputindex = (BUFFER_SIZE * 2) - 1;
		}
		--fileindex;
		isFinished = false;

	}
	return 0;

}

//return values:
//		 0 = success
//		-1 = string too long
//		-2 = Beginning of file reached
int Buffer::copyString(const size_t lastChars, char* position) {

	int tmp = ungetChar(lastChars);

	if (tmp == 0) {

		// write length of string in first position in front of string
		position[0] = lastChars;
		for (size_t i = 1; i <= lastChars; ++i) {
				position[i] = getChar();
		}
		position[lastChars + 1] = '\0';  // for easier output
	}
	return tmp;
}
